﻿using Google.Protobuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace Server
{
    class UserSession
    {
        static UniqueIDGenerator m_IDGenerator = new UniqueIDGenerator();
        static UniqueIDGenerator m_SeqIDGenerator = new UniqueIDGenerator();
        IPEndPoint m_ClientRemoteIP;
        MemoryStream m_SendStream = new MemoryStream(new byte[1024]);
        MemoryStream m_ReceiveStream;
        byte[] m_RecvBuffer = new byte[1024];
        byte[] m_MessageTypeBuffer = new byte[2];
        public uint m_UserID { private set; get; }
        public uint m_PlayerID { private set; get; }
        bool m_IsConnect;
        byte[] m_RecvProcessBuffer = new byte[1024];
        System.Random m_Rand = new System.Random();

        List<IMessage> m_RecvedMessages = new List<IMessage>(); // max num: 32
        public MessageDispatcher m_MessageDispatcher = new MessageDispatcher();
        PendingAckManager m_AckManager = new PendingAckManager();
        public float m_LastPing;
        Task m_KeepAliveTask;
        IMessage m_PingMessage = new Ping();

        public UserSession(IPEndPoint ep)
        {
            m_ReceiveStream = new MemoryStream(m_RecvBuffer);
            m_ClientRemoteIP = ep;
            m_MessageDispatcher.Register<Hello>(EMessageType.RUDP_HELLO, HelloCallback);
            m_MessageDispatcher.Register<Ack>(EMessageType.RUDP_ACK, AckCallback);
            m_MessageDispatcher.Register<C2S_PlayerJoin>(EMessageType.RUDP_C2S_PLAYER_JOIN, OnPlayerJoin);
            m_MessageDispatcher.Register<C2S_PlayerSync>(EMessageType.RUDP_C2S_PLAYER_SYNC, OnPlayerSync);
            m_MessageDispatcher.Register<Ping>(EMessageType.RUDP_PING, OnPing);
            m_AckManager.RegisterResendCallback(OnResend);
            m_UserID = m_IDGenerator.GetUniqueID();
            LogManager.Log("add user " + m_UserID);
            KeepAliveThread();
        }

        ~UserSession()
        {
            Stop();
        }

        public void Stop()
        {
            LogManager.Log("delete user " + m_UserID);
            ServerApp.Instance.RemovePlayer(m_UserID);
            m_MessageDispatcher.Unregister<Ping>(EMessageType.RUDP_PING, OnPing);
            m_MessageDispatcher.Unregister<C2S_PlayerSync>(EMessageType.RUDP_C2S_PLAYER_SYNC, OnPlayerSync);
            m_MessageDispatcher.Unregister<C2S_PlayerJoin>(EMessageType.RUDP_C2S_PLAYER_JOIN, OnPlayerJoin);
            m_MessageDispatcher.Unregister<Hello>(EMessageType.RUDP_HELLO, HelloCallback);
            m_MessageDispatcher.Unregister<Ack>(EMessageType.RUDP_ACK, AckCallback);
            if (m_KeepAliveTask != null)
            {
                m_KeepAliveTask.Dispose();
                m_KeepAliveTask = null; 
            }
            m_ReceiveStream.Close();
            m_SendStream.Close();
        }

        void KeepAliveThread()
        {
            m_KeepAliveTask = Task.Run(() =>
            {
                while (true)
                {
                    SendMessage(m_PingMessage);
                    Thread.Sleep(33);
                }
            });
        }

        void OnPing(Ping msg)
        {
            m_LastPing = CommonUtil.GetCurElapsedTime();
        }

        public void Tick()
        {
            m_AckManager.Tick();
            if (m_IsConnect)
            {
                if (CommonUtil.GetCurElapsedTime() - m_LastPing > 5)
                {
                    ServerApp.Instance.StopUser(this.m_ClientRemoteIP);
                }
            }
        }

        void OnResend(uint userId, IMessage msg)
        {
            if (userId != m_UserID)
            {
                return;
            }
            LogManager.Log("type=" + msg.GetType());
            var info = m_AckManager.GetAckInfo(userId, msg);
            SendMessage(msg, info == null ? 0 : info.m_SequenceID);
        }

        void HelloCallback(Hello msg)
        {
            this.SendMessage(new Hello());
        }

        public bool IsReliable(IMessage msg)
        {
            return MessageManager.Instance.IsReliable(msg);
        }

        void AckCallback(Ack msg)
        {
            m_IsConnect = true;
            m_AckManager.RecvAck(msg.SeqId, msg.AckBits);
        }

        void OnPlayerJoin(C2S_PlayerJoin msg)
        {
            if (!m_IsConnect)
            {
                return;
            }

            if (m_PlayerID == 0)
            {
                m_PlayerID = ServerApp.Instance.AddPlayer(m_UserID);
                Vector3 v = Vector3.zero;
                v.x = ((float)m_Rand.NextDouble() * 2f - 1f) * 5;
                v.z = ((float)m_Rand.NextDouble() * 2f - 1f) * 5;
                var player = ServerApp.Instance.GetPlayer(m_UserID);
                player.m_Pos = v;
                player.m_NickName = "Test " + m_UserID;
                var newPlayer = new S2C_PlayerJoin
                {
                    UserId = m_UserID,
                    NickName = player.m_NickName,
                    PosX = v.x,
                    PosY = v.y,
                    PosZ = v.z,
                    Host = m_ClientRemoteIP.Address.ToString(),
                    Port = (uint)m_ClientRemoteIP.Port
                };
                ServerApp.Instance.IterateUsers((UserSession user) =>
                {
                    user.SendMessage(newPlayer);
                });

                ServerApp.Instance.IterateUsers((UserSession user) =>
                {
                    var p = ServerApp.Instance.GetPlayer(user.m_UserID);
                    if (p == null)
                    {
                        return;
                    }
                    SendMessage(new S2C_PlayerJoin { UserId = user.m_UserID, NickName = p.m_NickName, PosX = p.m_Pos.x, PosY = p.m_Pos.y, PosZ = p.m_Pos.z });
                });

            }
        }

        void OnPlayerSync(C2S_PlayerSync msg)
        {
            var p = ServerApp.Instance.GetPlayer(m_UserID);
            if (p != null)
            {
                p.UpdatePos(msg.TickCount, new Vector3(msg.PosX, msg.PosY, msg.PosZ));
                LogManager.Log("userId=" + m_UserID + ", pos=" + p.m_Pos);
            }
        }

        public void SendMessage(IMessage msg, uint seqId = 0)
        {
            LogManager.Log(msg, true);
            m_SendStream.Position = 0;
            m_SendStream.Write(MessageManager.Instance.GetMessageTypeByteArray(msg), 0, 2);
            var bs = msg.ToByteArray();
            m_SendStream.Write(bs, 0, bs.Length);

            // resend or not. 
            uint newId = 0;
            if (IsReliable(msg))
            {
                if (seqId == 0)
                {
                    newId = m_SeqIDGenerator.GetUniqueID();
                    SetSeqID(msg, newId);
                }
                m_AckManager.AddInfo(m_UserID, seqId == 0 ? newId : seqId, msg);
            }
            ServerApp.Instance.m_Server.Send(m_SendStream.ToArray(), (int)m_SendStream.Position, m_ClientRemoteIP);
        }

        public EMessageType GetMessageType<T>(T msg)
            where T : IMessage
        {
            return MessageManager.Instance.GetMessageType(msg);
        }

        public void SetSeqID(IMessage msg, uint seqId)
        {
            MessageManager.Instance.SetSeqID(msg, seqId);
        }

        public IMessage ReceiveMessage(byte[] bs)
        {
            if (bs == null || bs.Length < 2)
            {
                return null;
            }

            int leftBytes = bs.Length;
            m_ReceiveStream.Position = 0;
            m_ReceiveStream.Write(bs, 0, bs.Length);
            m_ReceiveStream.Position = 0;
            m_ReceiveStream.Read(m_MessageTypeBuffer, 0, 2);
            leftBytes -= 2;
            var type = (EMessageType)BitConverter.ToUInt16(m_MessageTypeBuffer, 0);
            if (MessageManager.Instance.MessageDict.ContainsKey(type))
            {
                var msg = Activator.CreateInstance(MessageManager.Instance.MessageDict[type]) as IMessage;
                if (leftBytes > 0)
                {
                    Array.Copy(m_RecvBuffer, (int)m_ReceiveStream.Position, m_RecvProcessBuffer, 0, leftBytes);
                    msg.MergeFrom(m_RecvProcessBuffer, 0, leftBytes);
                }
                LogManager.Log(msg, true);

                if (IsReliable(msg))
                {
                    // test simulate miss packet
                    if (m_Rand.Next(0, 2) == 0 && GetMessageType(msg) != EMessageType.RUDP_C2S_PLAYER_JOIN)
                    {
                        //return null;
                    }

                    var curSeqId = MessageManager.Instance.GetSeqID(msg);
                    m_RecvedMessages.Add(msg);
                    if (m_RecvedMessages.Count > 32)
                    {
                        m_RecvedMessages.RemoveAt(0);
                    }
                    SendMessage(new Ack { SeqId = curSeqId, AckBits = MessageManager.Instance.GetAckBits(curSeqId, m_RecvedMessages) });
                }

                m_MessageDispatcher.Invoke(MessageManager.Instance.GetMessageType(msg), msg);
                return msg;
            }

            return null;
        }
    }
}
